﻿using Microsoft.CSharp;
using System;
using System.CodeDom.Compiler;
using System.Collections.Generic;
using System.Linq;
using System.Reflection;
using System.Text;
using System.Windows.Forms;

namespace MetaDemo
{
    class ReflectionException : Exception
    {
        public ReflectionException(String s) : base(s) { }
    }

    class MetaHelper
    {
        private static Type[] allowedFieldTypes = new Type[] { typeof(String), typeof(Int32), typeof(Boolean) };

        public static void SetMember(PropertyInfo propInfo, Object parent, String value)
        {
            Type t = propInfo.PropertyType;
            Object[] index = null;

            if (!allowedFieldTypes.Contains(t) || null == propInfo.GetSetMethod())
                throw new ReflectionException("Could not modify property of type " + propInfo.PropertyType);

            if (typeof(String).Equals(t))
            {
                try
                {
                    propInfo.SetValue(parent, value, index);
                }
                catch (Exception)
                {
                    throw new ReflectionException("Cannot change field value.");
                }
            }
            else if (typeof(Int32).Equals(t))
            {
                Int32 i = 0;
                try
                {
                    i = Int32.Parse(value);
                }
                catch (Exception)
                {
                    throw new ReflectionException("Entered value cannot be converted to a valid 32-bit integer!");
                }
                try
                {
                    propInfo.SetValue(parent, i, index);
                }
                catch (Exception)
                {
                    throw new ReflectionException("Cannot change field value.");
                }
            }
            else if (typeof(Boolean).Equals(t))
            {
                Boolean b = true;
                try
                {
                    b = Boolean.Parse(value);
                }
                catch (Exception)
                {
                    throw new ReflectionException("Entered value cannot be converted to a valid boolean!");
                }
                try
                {
                    propInfo.SetValue(parent, b, index);
                }
                catch (Exception)
                {
                    throw new ReflectionException("Cannot change field value.");
                }
            }
            else
            {
                throw new ReflectionException("Cannot modify field.");
            }
        }

    }



    public class CompileHelper
    {
        private static Object context = null;
        public static Object Context { get { return context; } set { context = value; } }

        static String template_begin = @"
                using System;
                using System.Windows.Forms;
                using System.Reflection;

                namespace MetaDemo {
                    class GeneratedAssembly_Main {
                        static void Main() {
            ";
        static String template_end = @"
                        }
                    }
                }
            ";
        public static Assembly createAssemblyViaCSharp(String userCode)
        {
            return createAssemblyViaCSharp(userCode, null);
        }

        public static Assembly createAssemblyViaCSharp(String userCode, Object context)
        {
            var contextCode = (null == context) ? "" : GetContextCode(context);

            var sourceCode = template_begin + contextCode + userCode + template_end;

            var results = Compile(sourceCode);

            var cec = results.Errors;
            if (cec.Count > 0)
            {
                var e = new CompileException();
                foreach (var ce in cec)
                {
                    e.AddErrorMessage(ce.ToString());
                }
                throw e;
            }

            return results.CompiledAssembly;
        }



        private static CompilerResults Compile(String code)
        {
            var csc = new CSharpCodeProvider(new Dictionary<string, string>() { { "CompilerVersion", "v3.5" } });
            var parameters = new CompilerParameters(GetLoadedAssemblies(), "some_name.exe", true);
            parameters.GenerateExecutable = true;
            parameters.GenerateInMemory = true;
            return csc.CompileAssemblyFromSource(parameters, code);
        }


        private static String GetContextCode(Object context)
        {
            Context = context;

            // Does not work: The type may be private in the assembly and thus not accessible for us.
            // String type = context.GetType().FullName;
            String type = typeof(Object).FullName;

            // <type> context = (<type>)MetaDemo.CompileHelper.Context;
            return "var" // Cannot use "type": we might not have the privilige to do so.
                + @" context = "
                + @"(" + type + @")"
                + @"MetaDemo.CompileHelper.Context;
            ";
        }

        private static String[] GetLoadedAssemblies()
        {
            var assemblies = new List<String>();
            foreach (var a in AppDomain.CurrentDomain.GetAssemblies())
                assemblies.Add(a.Location);

            return assemblies.ToArray<String>();
        }
    }

    class CompileException : Exception
    {
        private List<String> errors = new List<string>();
        public List<String> Errors { get { return errors; } }

        public void AddErrorMessage(String s) { errors.Add(s); }
    }
}
